Skip to content

Use uv to manage Python runtime and state#66

Merged
bradhe merged 15 commits intodevelopfrom
tasks/use-uv
Jul 18, 2025
Merged

Use uv to manage Python runtime and state#66
bradhe merged 15 commits intodevelopfrom
tasks/use-uv

Conversation

@bradhe
Copy link
Contributor

@bradhe bradhe commented Jul 16, 2025

This PR migrates the python execution pipeline to use uv instead of raw Python everywhere. It has some code for making sure uv exists/runs on the relevant host, too.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR migrates the Python execution pipeline from using raw Python and virtualenv to using uv for managing Python runtime and dependencies. The change introduces a new tower-uv crate that handles uv binary discovery/installation and simplifies the execution flow by leveraging uv's built-in dependency management.

Key Changes:

  • Added tower-uv crate with automatic uv binary discovery and installation capabilities
  • Removed virtualenv creation and pip dependency installation logic from tower-runtime
  • Simplified Python program execution to use uv run command instead of raw Python

Reviewed Changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
crates/tower-uv/src/lib.rs Core uv wrapper with binary discovery and execution methods
crates/tower-uv/src/install.rs Platform-specific uv binary download and installation logic
crates/tower-uv/Cargo.toml Dependencies for the new tower-uv crate
crates/tower-runtime/src/local.rs Updated to use uv instead of Python/pip/virtualenv
crates/tower-runtime/src/errors.rs Added error conversion from tower-uv errors
crates/tower-runtime/Cargo.toml Added tower-uv dependency
Cargo.toml Workspace updates for new dependencies and crate

} else {
None
}
} else{
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before opening brace. Should be } else {

Suggested change
} else{
} else {

Copilot uses AI. Check for mistakes.
}

// First, check if uv is already in the PATH
let output = Command::new("which")
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using which command is not portable across platforms (not available on Windows). Consider using a cross-platform solution or adding platform-specific logic.

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +222
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation - uses tabs instead of spaces. Should match the project's indentation style.

Suggested change
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +222
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation - uses tabs instead of spaces. Should match the project's indentation style.

Suggested change
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))
let package_name = extract_package_name(archive.clone());
Ok(path.join(package_name).join("uv"))

Copilot uses AI. Check for mistakes.
std::fs::create_dir_all(&target_dir)?;

// Extract the file with proper error handling for compression
let filename = entries[entry_index].filename().as_str().unwrap_or("uv").to_string();
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation - uses tabs instead of spaces. Should match the project's indentation style.

Suggested change
let filename = entries[entry_index].filename().as_str().unwrap_or("uv").to_string();
let filename = entries[entry_index].filename().as_str().unwrap_or("uv").to_string();

Copilot uses AI. Check for mistakes.

async fn download_uv_archive(path: &PathBuf, archive: String) -> Result<PathBuf, Error> {
debug!("Downloading UV archive: {}", archive);
let url = format!("https://github.com/astral-sh/uv/releases/download/0.7.13/{}", archive);
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UV version '0.7.13' is hardcoded. Consider making this configurable or using a constant to make updates easier.

Suggested change
let url = format!("https://github.com/astral-sh/uv/releases/download/0.7.13/{}", archive);
let url = format!("https://github.com/astral-sh/uv/releases/download/{}/{}", UV_VERSION, archive);

Copilot uses AI. Check for mistakes.
Comment on lines +169 to +183
if let Some(version_part) = line.split_whitespace()
.find(|part| part.contains('.') && part.chars().next().unwrap_or('0').is_ascii_digit())
{
let version_clean = version_part.trim_matches(|c: char| !c.is_ascii_digit() && c != '.');
let parts: Vec<&str> = version_clean.split('.').collect();

if parts.len() >= 2 {
if let (Ok(major), Ok(minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
return major > req_major || (major == req_major && minor >= req_minor);
}
}
}
}
false
}
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Complex nested logic for version parsing. Consider extracting this into a separate helper function for better readability and testability.

Suggested change
if let Some(version_part) = line.split_whitespace()
.find(|part| part.contains('.') && part.chars().next().unwrap_or('0').is_ascii_digit())
{
let version_clean = version_part.trim_matches(|c: char| !c.is_ascii_digit() && c != '.');
let parts: Vec<&str> = version_clean.split('.').collect();
if parts.len() >= 2 {
if let (Ok(major), Ok(minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
return major > req_major || (major == req_major && minor >= req_minor);
}
}
}
}
false
}
if let Some((major, minor)) = Self::extract_version_parts(line) {
return major > req_major || (major == req_major && minor >= req_minor);
}
}
false
}
/// Extract major and minor version numbers from a single line of the version string
fn extract_version_parts(line: &str) -> Option<(u32, u32)> {
if let Some(version_part) = line.split_whitespace()
.find(|part| part.contains('.') && part.chars().next().unwrap_or('0').is_ascii_digit())
{
let version_clean = version_part.trim_matches(|c: char| !c.is_ascii_digit() && c != '.');
let parts: Vec<&str> = version_clean.split('.').collect();
if parts.len() >= 2 {
if let (Ok(major), Ok(minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
return Some((major, minor));
}
}
}
None
}

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be rewritten eventually in general, so will skip it for now.

}

fn make_env_vars(ctx: &tower_telemetry::Context, env: &str, cwd: &PathBuf, is_virtualenv: bool, secs: &HashMap<String, String>, params: &HashMap<String, String>, other_env_vars: &HashMap<String, String>) -> HashMap<String, String> {
fn make_env_vars(ctx: &tower_telemetry::Context, env: &str, cwd: &PathBuf, secs: &HashMap<String, String>, params: &HashMap<String, String>, other_env_vars: &HashMap<String, String>) -> HashMap<String, String> {
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Function signature is very long with many parameters. Consider grouping related parameters into a struct for better maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise will ignore this for now, I don't think it's necessary.

@bradhe bradhe marked this pull request as ready for review July 16, 2025 11:40
Copy link
Contributor

@konstantinoscs konstantinoscs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run a build-tower-runner-binaries on this? I see changes to cargo.toml and I'm thinking that we should have some kind of way to safeguard against regressing from pure rustls...


// If we're in a virtual environment, we need to add the bin directory to the PATH so that we
// can find any executables that were installed there.
if is_virtualenv {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So uv takes care of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly.

@bradhe
Copy link
Contributor Author

bradhe commented Jul 16, 2025

Spoke with @konstantinoscs offline, we aren't able to product runner binaries with PRs in this repo because the relevant code needs to land in main first; however, I added a CI check for the rustls change that he is worried about!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesomeeee

@bradhe bradhe merged commit ed0ec08 into develop Jul 18, 2025
3 checks passed
@bradhe bradhe deleted the tasks/use-uv branch July 18, 2025 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants